Add support for unified hosts#1307
Conversation
cf52930 to
21f56ce
Compare
| // It relies on the workspace ID being set if and only if a workspace client is being used. | ||
| func workspaceOrgIdVisitor(cfg *Config) func(r *http.Request) error { | ||
| return func(r *http.Request) error { | ||
| if cfg.WorkspaceId != "" { |
There was a problem hiding this comment.
suggestion: let's check what happens if a user specifies the wrong workspace ID for a given workspace host.
There was a problem hiding this comment.
Just checked, the header is ignored by workspace hosts.
| return a.ln.Close() | ||
| } | ||
|
|
||
| // validateArg ensures that the OAuthArgument is either a WorkspaceOAuthArgument |
| } | ||
|
|
||
| var ErrNotAccountClient = errors.New("invalid Databricks Account configuration") | ||
| var ErrWorkspaceIdInAccountClient = errors.New("WorkspaceId must not be set when using AccountClient") |
There was a problem hiding this comment.
These changes need to happen in codegen.
There was a problem hiding this comment.
will split these out, keeping them in here just for tests & review for now
| } | ||
| if cfg.IsAccountClient() { | ||
| hostType := cfg.GetHostType() | ||
| if hostType == config.AccountHost || hostType == config.UnifiedHost && cfg.WorkspaceId == "" { |
There was a problem hiding this comment.
suggestion: separate check with a better error message than the (already poor) status quo
mgyucht
left a comment
There was a problem hiding this comment.
Few comments/suggestions, let me know what you think
| }, | ||
| } | ||
|
|
||
| // Workspace IDs must be provided explicitly in request headers when using unified hosts. |
There was a problem hiding this comment.
suggestion: a bit more clarity on this doccomment, for example:
and when calling workspace-level APIs. Furthermore, workspace ID must not be provided when calling account-level APIs.
| if err != nil { | ||
| return nil, fmt.Errorf("resolve host: %w", err) | ||
| } | ||
| err := cfg.azureEnsureWorkspaceUrl(ctx, c) |
There was a problem hiding this comment.
Stupid question: we can use Azure MSI with account-level APIs as well, right?
There was a problem hiding this comment.
Yes, but we do various checks that basically ensure that azureEnsureWorkspaceUrl only takes effect if the client is configured for workspace APIs. But just to remove any doubt I've replaced the calls here and in auth_gcp_id with ClientType(). If there's a desire to remove these in the future we can build on the research done here, but we don't need to block this PR on it.
| // client in the future. | ||
| // | ||
| // Deprecated: Use HostType() instead. | ||
| func (c *Config) ConfigType() ConfigType { |
There was a problem hiding this comment.
It's needed by Terraform. We could use it in azure_msi and gcp_id auth.
| // ConfigType returns the type of config that the client is configured for. | ||
| // Returns InvalidConfig if the config is invalid. | ||
| // Use of this function should be avoided where possible, because we plan | ||
| // to remove WorkspaceClient and AccountClient in favo of a single unified |
There was a problem hiding this comment.
| // to remove WorkspaceClient and AccountClient in favo of a single unified | |
| // to remove WorkspaceClient and AccountClient in favor of a single unified |
| // to remove WorkspaceClient and AccountClient in favo of a single unified | ||
| // client in the future. | ||
| // | ||
| // Deprecated: Use HostType() instead. |
There was a problem hiding this comment.
I don't get the sense that this is deprecated yet, though I do agree that it would be deprecated once we move to the unified API endpoint. Until then, there isn't really an alternative to this, so it doesn't make sense to me to recommend using HostType() directly.
There was a problem hiding this comment.
Yeah, I agree. I was trying to discourage usage, but I think marking it as deprecated is too much when there's no viable alternative yet in some cases.
| } | ||
| host := c.CanonicalHostName() | ||
| if c.IsAccountClient() { | ||
| switch hostType := c.HostType(); hostType { |
There was a problem hiding this comment.
I think you can pattern match on the result of the assignment without repeating it in Golang
| switch hostType := c.HostType(); hostType { | |
| switch hostType := c.HostType() { |
There was a problem hiding this comment.
I'm using it in the error message, but I can just call the function twice if that's preferred?
| } | ||
| host := c.CanonicalHostName() | ||
| if c.IsAccountClient() { | ||
| switch hostType := c.HostType(); hostType { |
There was a problem hiding this comment.
Ditto
| switch hostType := c.HostType(); hostType { | |
| switch hostType := c.HostType() { |
| // UnifiedOAuthArgument is an interface that provides the necessary information | ||
| // to authenticate using OAuth to a host that supports both account and workspace APIs. | ||
| type UnifiedOAuthArgument interface { | ||
| OAuthArgument |
There was a problem hiding this comment.
Stupid question: this interface is identical to AccountOAuthArgument, can it just implement that interface instead?
| // UnifiedOAuthArgument is an interface that provides the necessary information | ||
| // to authenticate using OAuth to a host that supports both account and workspace APIs. | ||
| type UnifiedOAuthArgument interface { | ||
| OAuthArgument |
There was a problem hiding this comment.
For now, I don't see the use of having an extra interface. We can just document that the AccountOAuthArgument interface can be used whether authenticating to an accounts host or a unified host. If there were a net new behavior that only applied to unified hosts, we could add something like this. So let's remove this (and maybe touch up the documentation on the accounts interface/implementation).
| case AccountOAuthArgument: | ||
| endpoints, err = a.endpointSupplier.GetAccountOAuthEndpoints( | ||
| a.ctx, argg.GetAccountHost(), argg.GetAccountId()) | ||
| case UnifiedOAuthArgument: |
There was a problem hiding this comment.
OK, I see what I think is going a bit wrong here. I feel like the OAuthEndpointSupplier should actually be provided as part of the OAuthArgument. That way the PersistantAuth doesn't need to know which EndpointSupplier method to call for a given OAuth argument, and it is fixed per OAuth argument.
There was a problem hiding this comment.
Agreed, though we can treat this as a follow-up.
| if c.AccountID != "" && c.isTesting { | ||
| return AccountHost | ||
| } |
There was a problem hiding this comment.
| if c.AccountID != "" && c.isTesting { | |
| return AccountHost | |
| } | |
| // TODO: Refactor tests so that this is not needed. | |
| if c.AccountID != "" && c.isTesting { | |
| return AccountHost | |
| } |
|
If integration tests don't run automatically, an authorized user can run them manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
## What changes are proposed in this pull request? This PR adds support for unified host: - Separates client type from host type determination, deprecating is_account_client and replacing it with host_type and client_type properties using new HostType and ClientType enums - Adds an experimental flag to indicate if a host is unified: experimental_is_unified_host - Adds a workspace_id attribute to Config, which is necessary for workspace clients that talk to unified hosts - Adds get_unified_endpoints() function, which is used in the OIDC endpoint resolution logic to discover OAuth endpoints on unified hosts - Adds header injection logic in ApiClient which adds an X-Databricks-Org-Id header to requests made by workspace clients on unified hosts - Adds comprehensive test coverage including unit tests for host/config type detection, OIDC endpoint resolution, header injection, and integration tests Similar to what is done for databricks/databricks-sdk-go#1307 ## How is this tested? - Unit tests - Manually integration tested <img width="1275" height="331" alt="image" src="https://github.com/user-attachments/assets/f456e104-6210-43a4-a99a-1c2ff0b134a2" /> - All existing integration tests pass Note: Integration test would be added in another PR once the test infra supports spog
## Changes <!-- Brief summary of your changes that is easy to understand --> - Add support for unified host with experimental flag. - Prompt for workspace id as it's not part of the host - Write the IDs and flag in the created profile. - Depends on databricks/databricks-sdk-go#1307 ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. --> This support is required for enabling unified hosts which Databricks free edition uses. ## Tests <!-- How have you tested the changes? --> - Unit tests - Manual E2E tests -- CUJ for CLI U2M `databricks auth login --host <spog-host> --experimental-is-unified-host` prompts for account and workspace id followed by opening web browser. The IDs and flag is stores in the config. The subsequent cli operations eg: databricks clusters list --profile "above-profile" works.
## What changes are proposed in this pull request? This PR adds support for unified host: - Separates client type from host type determination, deprecating `isAccountClient` and replacing it with `getHostType()` and `getClientType()` methods using new `HostType` and `ClientType` enums - Adds an experimental flag to indicate if a host is unified: `experimentalIsUnifiedHost` - Adds a `workspaceId` attribute to DatabricksConfig, which is necessary for workspace clients that talk to unified hosts - Adds `getUnifiedOidcEndpoints()` function, which is used in the OIDC endpoint resolution logic to discover OAuth endpoints on unified hosts - Adds header injection logic in DatabricksConfig.authenticate() which adds an `X-Databricks-Org-Id` header to requests made by workspace clients on unified hosts - Adds comprehensive test coverage including unit tests for host/client type detection, OIDC endpoint resolution, header injection, and integration tests Similar to what is done for: - [databricks/databricks-sdk-py#1135](databricks/databricks-sdk-py#1135) - [databricks/databricks-sdk-go#1307](databricks/databricks-sdk-go#1307) ## How is this tested? - Unit tests - Manually E2E tested with a spog profile
## Changes <!-- Brief summary of your changes that is easy to understand --> - Add support for unified host with experimental flag. - Prompt for workspace id as it's not part of the host - Write the IDs and flag in the created profile. - Depends on databricks/databricks-sdk-go#1307 ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. --> This support is required for enabling unified hosts which Databricks free edition uses. ## Tests <!-- How have you tested the changes? --> - Unit tests - Manual E2E tests -- CUJ for CLI U2M `databricks auth login --host <spog-host> --experimental-is-unified-host` prompts for account and workspace id followed by opening web browser. The IDs and flag is stores in the config. The subsequent cli operations eg: databricks clusters list --profile "above-profile" works.
What changes are proposed in this pull request?
This PR adds support for unified hosts, i.e. hosts that support both workspace and account methods. To do this, it:
isAccountClientand replacing it withHostTypeandConfigType.experimental_is_unified_hostNote: workspace_client.go and account_client.go are generated and will be updated in a separate PR upstream.
How is this tested?
unit & intg tests